#!/usr/bin/python
#coding=utf-8 

# _Client pbs基类
# _command 执行命令函数
# _put 执行分发函数
# _get 执行下载函数
# Mythread 线程类

#code by eleven.i386

import os
import sys
import md5
import time
import Queue
import getopt 
import paramiko
import threading

_timeout = ''
cfile = ''
ifile = ''
num = 0
com_list = []
file_list = []
wordip = []
err = []

# 线程基类
class Mythread(threading.Thread):
    def __init__(self,queue):
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        try:
            self.ip = self.queue.get()
            self.client = _Client(self.ip)
            self.client._start(com_list)
            self.queue.task_done()
        except Exception,e:
            pass
            #print e # 错误提示

class _Client():
    

    # 实例化 SSHClient对象，采用自动添加主机信息，避免yes提示，

    def __init__(self,ip):
        self.ip = ip[0]
        self.port = ip[1]
        self.user = ip[2]
        self.passwd = ip[3]
        self.info = []
        self.a = []
        try:
            self.c = paramiko.SSHClient()    
            self.c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 
            self.c.load_system_host_keys()
        except Exception,e:
            err.append((self.ip,e))
            return e

    # md5 校验
    def md5sum(self,fjob):
        c = md5.md5()
        while 1:
            fp = fjob.read(8096)
            if not fp:
                break
            c.update(fp)
        return c.hexdigest()

    def _callback(self,a,b):
        sys.stdout.write('Data Transmission %10d [%3.2f%%]\r' %(a,a*100./int(b)))
        sys.stdout.flush()
        self.a.append((a,b))

    def _close(self):
        for i in self.info:
            if len(i) == 15:continue
            print i
        self.c.close()


    #根据file后面跟着的参数 决定是上传文件还是下载文件
    def _start(self,cmd):
        try:
            global num
            self.c.connect(self.ip,port=int(self.port),username=str(self.user),password=str(self.passwd),timeout=_timeout)
            self.sftp = self.c.open_sftp()
            num+=1 
        except Exception,e:
            err.append((self.ip,e))
            return e
        print('\033[40;32;1m<info>: Create connect %s \033[0m' %(self.ip))
        for cmd_i in cmd:
            _cmd = cmd_i.split(':::')
            if _cmd[0] == 'com':self._command(_cmd[1])
            if _cmd[0] == 'file':
                if _cmd[1].endswith('push'):self._put(_cmd[1])
                if _cmd[1].endswith('get'):self._get(_cmd[1])
        self._close()
        
    # 根据配置文件com 关键字 定义的命令。执行
    def _command(self,cmd):
        self.info.append('\033[40;32;1m<info>:%s  \033[0m' %(cmd))
        self.i,self.o,self.e = self.c.exec_command(str(cmd),_timeout)
        self.info.append('\033[40;31;1m%s \033[0m' %(self.e.read().replace('\n','')))
        self.info.append('\033[40;32;1m%s \033[0m' %(self.o.read().replace('\n','')))

        #下载文件函数
    def _get(self,cmd):
        global num
        self.file_list = cmd.split(' ')
        self.rfile = self.file_list[0]
        self.lfile = os.path.join(self.file_list[1],self.ip)
        #用于创建本地目录，依据远程目录结构
        def _mkdir(x):
            if not os.path.exists(x.replace(os.path.split(self.rfile)[0],self.lfile)):
                self.info.append('\033[40;32;1m<info>: Create %s \033[0m' %(x.replace(os.path.split(self.rfile)[0],self.lfile)))
                os.mkdir(x.replace(os.path.split(self.rfile)[0],self.lfile))
                
        #递归发现目录，并下载文件
        def _get_dir(x):
            if oct(self.sftp.lstat(x).st_mode)[:-3] == '040':
                _mkdir(x)
                for i in self.sftp.listdir(x):
                    if oct(self.sftp.lstat(os.path.join(x,i)).st_mode)[:-3] == '040':
                         _get_dir(os.path.join(x,i))
                    else:
                        self.info.append('\033[40;32;1m<info>: get %s to %s \033[0m' %(os.path.join(x,i),os.path.join(x.replace(os.path.split(self.rfile)[0],self.lfile),i)))
                        self.sftp.get(os.path.join(x,i),os.path.join(x.replace(os.path.split(self.rfile)[0],self.lfile),i),self._callback)
                        lfp = open(os.path.join(x.replace(os.path.split(self.rfile)[0],self.lfile),i))
                        rfp = self.sftp.open(os.path.join(x,i))
                        self.info.append('%s: %s Bytes,GetSize: %s Bytes, Remote File MD5: %s ,Local File MD5: %s' \
                                        %(os.path.split(self.rfile)[1],self.a[-1][0],self.a[-1][1],self.md5sum(rfp),self.md5sum(lfp)))
            else:
                self.info.append('\033[40;32;1m<info>: get %s to %s \033[0m' %(x,x.replace(os.path.split(self.rfile)[0],self.lfile)))
                self.sftp.get(x,x.replace(os.path.split(self.rfile)[0],self.lfile),self._callback)
                lfp = open(x.replace(os.path.split(self.rfile)[0],self.lfile))
                rfp = self.sftp.open(x)
                self.info.append('%s: %s Bytes,GetSize: %s Bytes, Remote File MD5: %s ,Local File MD5: %s'\
                                %(os.path.split(self.rfile)[1],self.a[-1][0],self.a[-1][1],self.md5sum(rfp),self.md5sum(lfp)))

        if not os.path.exists(self.lfile):os.mkdir(self.lfile)
        if oct(self.sftp.lstat(self.rfile).st_mode)[:-3] == '040':
            for i in self.sftp.listdir(self.rfile):
                if not os.path.exists(self.rfile.replace(os.path.split(self.rfile)[0],self.lfile)):
                    os.mkdir(self.rfile.replace(os.path.split(self.rfile)[0],self.lfile))
                else:_get_dir(os.path.join(self.rfile,i))
        else:_get_dir(self.rfile)

    # 根据 配置文件 file 关键字 定义的路径，分发文件
    def _put(self,cmd):
        global num
        self.file_list = cmd.split(' ')
        self.lfile = self.file_list[0]
        self.rfile = self.file_list[1]
        # 检查远程主机是否存在指定路径
        try:
            self.sftp.listdir_attr(self.rfile)
        except Exception,e:
            num-= 1
            err.append((self.ip,self.rfile,e))
            return e

            # 判断本地文件 是目录还是文件
        if os.path.isdir(self.lfile):
            try:
                    # 额， 这里主要是我没有找到paramiko 的类似 os.path.exists() 方法，
                    #所以用stat代替，根据返回值捕获异常来判断远程主机是否存在同样的目录
                    
                self.sftp.stat(self.lfile.replace(os.path.split(self.lfile)[0],self.rfile,1))
            except IOError:
                    
                    # 如果爆出异常， 就判断为 远程主机不存在该目录， 创建之...

                self.info.append('\033[40;32;1m<info>: remote host %s mkdir %s \033[0m' %(self.ip,os.path.split(self.lfile)[1]))
                try:
                    self.sftp.mkdir(os.path.join(self.rfile,os.path.split(self.lfile)[1]))
                except Exception,e:
                    num-=1
                    err.append((self.ip,e))
                    return e
            for root_path,dirs,files in os.walk(self.lfile):
                    
                    # 去掉dirs列表中空行

                for i in dirs:
                    if len(i) < 1:
                        continue
                    else:
                        try:
                                # 同样 判断远程主机是否存在 目录..
                            self.sftp.stat(os.path.join(root_path.replace(os.path.split(self.lfile)[0],self.rfile,1),i))
                        except IOError:
                            self.info.append('\033[40;32;1m<info>: remote host %s mkdir %s \033[0m' %(self.ip,\
                                            os.path.join(root_path.replace(os.path.split(self.lfile)[0],self.rfile,1),i)))
                            try:
                                self.sftp.mkdir(os.path.join(root_path.replace(os.path.split(self.lfile)[0],self.rfile,1),i))
                            except Exception,e:
                                err.append((self.ip,e))
                                num-=1
                                return e

                    # 总的原则是,先判断本地路径是文件还是目录,如果是目录,判断远程主机是否存在该目录,
                    # 如果存在,跳过, 不存在,就创建
                    
                    # 使用paramiko 的sftp的put方法,将文件上传到服务器上


                for i in files:
                    try:
                        self.info.append('\033[40;32;1m<info>: Put File %s to %s \033[0m' %(os.path.join(root_path,i),\
                                        os.path.join(root_path.replace(os.path.split(self.lfile)[0],self.rfile,1),i)))
                        self.sftp.put(os.path.join(root_path,i),os.path.join(root_path.replace(os.path.split(self.lfile)[0],self.rfile,1),i),self._callback)
                        lfp = open(os.path.join(root_path,i))
                        rfp = self.sftp.open(os.path.join(root_path.replace(os.path.split(self.lfile)[0],self.rfile,1),i))
                        self.info.append('%s: %s Bytes,PutSize: %s Bytes,local File MD5: %s ,Remote File MD5: %s' \
                                        %(os.path.split(self.lfile)[1],self.a[-1][0],self.a[-1][1],self.md5sum(lfp),self.md5sum(rfp)))
                    except Exception,e:
                        err.append((self.ip,e))
                        num-=1
                        return e
        else:
            try:    
                self.info.append('\033[40;32;1m<info>: Put File %s to %s \033[0m' %(self.lfile,os.path.join(self.rfile,os.path.split(self.lfile)[1])))
                self.sftp.put(self.lfile,os.path.join(self.rfile,os.path.split(self.lfile)[1]),self._callback)
                lfp = open(self.lfile)
                rfp = self.sftp.open(os.path.join(self.rfile,os.path.split(self.lfile)[1]))
                self.info.append('%s: %s Bytes,PutSize: %s Bytes,local File MD5: %s ,Remote File MD5: %s' %(os.path.split(self.lfile)[1],self.a[-1][0],self.a[-1][1],\
                                self.md5sum(lfp),self.md5sum(rfp)))
            except Exception,e:
                err.append((self.ip,e))
                num-=1
                return e

def _file(comfile,ipfile):
    try:
        com_fp = open(comfile,'r').readlines()
    except IOError:
        print 'Open '+comfile+'is Fail!,Make sure that file is exist!'
    try:
        ip_fp = open(ipfile,'r').readlines()
    except IOError:
        print 'Open '+ipfile+'is Fail!,Make sure that file is exist!'
    for i in ip_fp:
        if i.startswith('#') or len(i) < 2:continue
        wordip.append(i.replace('\n','').split(' '))
    for c in com_fp:
        if c.startswith('#') or len(c) < 2 :continue
        else:com_list.append(c.replace('\n',''))

    
def _help():
    print '='*100
    print '\t\t\t AutoDis Tool\n'
    print '-c | --conf\t\tSpecify the command file'
    print '-l | --iplst\t\tSpecify the ip list'
    print '-h | --help\t\tShow the message'
    print '-t | --timeout\t\tSet connect timeout'

    print '\t\t\t\t\tcode by eleven.i386'
    print '='*100

def main():
    global cfile,ifile
    opts,args = getopt.getopt(sys.argv[1:],'hc:l:t:','--conf=,--iplst=,--timeout')
    for name,value in opts:
        if name in ('-h','--help'):
            _help()
        if name in ('-c','--conf'):
            cfile = value
        if name in ('-l','--iplst'):
            ifile = value
        if name in ('-t','--timeout'):
            _timeout = value

    if len(sys.argv) < 2:_help();sys.exit(1)
    if len(cfile) < 1 or len(ifile) < 1:sys.exit(0)
    else:
        _file(cfile,ifile)

        # 根据ip数量分配线程数量
        queue = Queue.Queue() # 创建队列实例
        for i in wordip:
            queue.put(i) # 将ip信息填充队列

       
        print 'Ready .....'

        for thread in xrange(len(wordip)): # 根据ip数量确定并发多少线程
            mythread = Mythread(queue)
            mythread.setDaemon(True)
            mythread.start()


        queue.join()

        #汇总统计信息
        x = '-'*50
        print '%sResult%s' %(x,x)
        print '%sTotal: %s , Ok: %s, Error: %s' %(' '*40,len(wordip),num,len(err))
        if len(err) == 0:pass
        else:
            print '%sError%s' %(x,x)
            for i in err:
                print i
            
if __name__ == '__main__':main()
